package com.github.davidfantasy.mybatisplus.generatorui.controller;

import cn.hutool.core.io.resource.ResourceUtil;
import com.github.davidfantasy.mybatisplus.generatorui.CustomConfig;
import com.github.davidfantasy.mybatisplus.generatorui.common.Result;
import com.github.davidfantasy.mybatisplus.generatorui.common.ResultGenerator;
import com.github.davidfantasy.mybatisplus.generatorui.dto.MpgGenCodeDto;
import com.github.davidfantasy.mybatisplus.generatorui.mbp.MbpGenerator;
import com.github.davidfantasy.mybatisplus.generatorui.util.PathUtil;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.*;
import java.util.stream.Stream;

@RestController
@RequestMapping("/api/mbp-generator")
public class MbpGeneratorController {

    @Autowired
    private MbpGenerator mbpGenerator;

    private static AntPathMatcher matcher = new AntPathMatcher();

    @PostMapping("/gen-code")
    public Result genCode(@RequestBody MpgGenCodeDto dto) {
        mbpGenerator.genCodeBatch(dto.getGenSetting(), dto.getTables());

        Map<String,String> vars = new HashMap<>();

        vars.put("@custom.ScmsPkg@", CustomConfig.scmsPkg);
        vars.put("@custom.CommonsPkg@", CustomConfig.commonsPkg);
        vars.put("@custom.BasePkg@", CustomConfig.basePkg);
        vars.put("@custom.ModuleName@", CustomConfig.moduleName);
        vars.put("@custom.ApiPkg@", CustomConfig.basePkg + "." + CustomConfig.moduleName + "." + CustomConfig.apiPkg);
        vars.put("@custom.SvcApiPkg@", CustomConfig.basePkg + "." + CustomConfig.moduleName + "." + CustomConfig.svcApiPkg);

        genProject(dto.getGenSetting().getRootPath(), vars);
        toMicrosvc(dto.getGenSetting().getRootPath(), vars);

        return ResultGenerator.genSuccessResult();
    }

    private void genProject(String codePath, Map<String,String> vars){
        String moduleDir = PathUtil.joinPath(CustomConfig.basePkg + "." + CustomConfig.moduleName).replace(".", File.separator);
        try {
            writer("classpath:project/AppStarter.java.btl", codePath + "/src/main/java/" + moduleDir + "/AppStarter.java", vars);
            writer("classpath:project/bootstrap.yml.btl", codePath + "/src/main/resources/bootstrap.yml", vars);
            writer("classpath:project/logback.xml.btl", codePath + "/src/main/resources/logback.xml", vars);
            writer("classpath:project/pom.xml", codePath + "/pom.xml", vars);
            writer("classpath:project/.gitignore", codePath + "/.gitignore", vars);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private void toMicrosvc(String codePath, Map<String,String> vars){
        File sourceDir = new File(codePath + "/src");
        File entityDir = new File(codePath + File.separator + CustomConfig.moduleName + "-entity/src");
        File apiDir = new File(codePath + File.separator + CustomConfig.moduleName + "-api/src");
        File svcDir = new File(codePath + File.separator + CustomConfig.moduleName + "-svc/src");
        try {
            copyDir(sourceDir, entityDir, Arrays.asList("/**/src/test/**","/**/src/main/resources/**","/**/mapper/**","/**/service/**","/**/svcapi/**","/**/api/**","/**/AppStarter.java"));
            copyDir(sourceDir, apiDir, Arrays.asList("/**/src/test/**","/**/src/main/resources/**","/**/mapper/**","/**/service/**","/**/svcapi/**","/**/entity/**","/**/AppStarter.java"));
            copyDir(sourceDir, svcDir, Arrays.asList("/**/entity/**", "/**/api/**"));
            writer("classpath:project/pom-p.xml", codePath + "/pom.xml", vars);
            writer("classpath:project/pom-entity.xml", codePath + File.separator + CustomConfig.moduleName + "-entity/pom.xml", vars);
            writer("classpath:project/pom-api.xml", codePath + File.separator + CustomConfig.moduleName + "-api/pom.xml", vars);
            writer("classpath:project/pom-svc.xml", codePath + File.separator + CustomConfig.moduleName + "-svc/pom.xml", vars);
            deleteDir(codePath + "src");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private void writer(String tmp, String codeFile, Map<String,String> vars) throws IOException {
        Writer writer = new FileWriter(codeFile);
        BufferedReader reader = ResourceUtil.getUtf8Reader(tmp);
        String line = "";
        while ((line = reader.readLine()) != null) {
            String result = line;
            for (Map.Entry<String,String> entity : vars.entrySet()){
                result = result.replaceAll(entity.getKey(), entity.getValue());
            }
            writer.write(result);
            writer.write(System.lineSeparator());
        }
        writer.flush();
    }

    public void copyDir(File sourceDir, File targetDir, List<String> excludes) throws IOException {

        String sourceFilePath = sourceDir.getPath();
        for (String exclude : excludes) {
            if (matcher.match(exclude, sourceFilePath)) {
                return;
            }
        }

        if (!targetDir.exists()) {
            targetDir.mkdirs();
        }
        File[] files = sourceDir.listFiles();
        if (files != null) {
            for (File file : files) {
                File targetFile = new File(targetDir, file.getName());
                if (file.isDirectory()) {
                    copyDir(file, targetFile, excludes);
                } else {
                    copyFile(file, targetFile, excludes);
                }
            }
        }

    }

    public void copyFile(File sourceFile, File targetFile, List<String> excludes) throws IOException {
        String sourceFilePath = sourceFile.getPath();
        for (String exclude : excludes) {
            if (matcher.match(exclude, sourceFilePath)) {
                return;
            }
        }
        Files.copy(sourceFile.toPath(), targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
    }

    @SneakyThrows
    private void deleteDir(String path) {
        try (Stream<Path> walk = Files.walk(Paths.get(path))) {
            walk.sorted(Comparator.reverseOrder()).forEach(p -> {
                try {
                    Files.delete(p);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            });
        }
    }

}
